/***
* ==++==
*
* Copyright (c) Microsoft Corporation.  All rights reserved.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* uri_tokenizer.h - A uri component tokenizing class implementation
*
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#pragma once

#include <map>
#include <vector>

#include "uri.h"
#include "xxpublic.h"

namespace http
{

/// <summary>
/// Represents an optional value from a query. When used with uri_tokenizer, the
/// the lack of a key-value parameter, or an "empty string" signifies that the parameter
/// was not provided
///
/// Ex: 'foo&bar=5', 'foo=&bar=5', 'bar=5' would all signify that 'foo' was not provided
///
/// </summary>
/// <remarks>
/// We could also provide a 'flag' class that would work like so.
///
/// Ex: 'foo&bar=5' -> foo: true, 'bar=5' -> foo: false
/// </remarks>
template<typename _t>
class optional
{
public:
    bool has_value;
    _t value;
};

/// <summary>
/// A utility for extracting strongly typed data from a uri resource (path, query, fragment)
/// </summary>
/// <remarks>
/// The parsing functionality is based on objects providing an overload of:
///
/// std::basic_istringstream[char_t]& operator>> (std::basic_istringstream[char_t]& &is, foo_class &s);
/// </remarks>
class uri_tokenizer
{
public:

    /// <summary>
    /// Creates a tokenizer for the given uri
    /// </summary>
    uri_tokenizer(const uri &u) : m_path_tokens(uri::split_path_and_wdecode(u.path())), m_query_tokens(uri::split_query_and_wdecode(u.query())) {}

    /// <summary>
    /// Creates a tokenizer for the given path and query.
    /// </summary>
    uri_tokenizer(const std::wstring &path, const std::wstring &query) : m_path_tokens(uri::split_path(path)), m_query_tokens(uri::split_query(query)) {}

    /// <summary>
    /// Creates a tokenizer for the given path and query.
    /// </summary>
    uri_tokenizer(const std::string &path, const std::string &query) : 
		m_path_tokens(uri::split_path(utilities::conversions::utf8_to_utf16(path))), m_query_tokens(uri::split_query(utilities::conversions::utf8_to_utf16(query))) 
	{
	}
    
	/// <summary>
    /// Attempts to match the first token of the path with the given reference
    /// </summary>
    template<typename _t1>
    bool match_path(_t1 &t1_ref)
    {
        if (m_path_tokens.size() < 1)
        {
            return false;
        }

        return this->bind(m_path_tokens[0], t1_ref);
    }

    /// <summary>
    /// Attempts to match the first and second tokens of the path with the given references
    /// </summary>
    template<typename _t1, typename _t2>
    bool match_path(_t1 &t1_ref, _t2 &t2_ref)
    {
        if (m_path_tokens.size() < 2)
        {
            return false;
        }

        return
            this->bind(m_path_tokens[0], t1_ref) &&
            this->bind(m_path_tokens[1], t2_ref);
    }

    /// <summary>
    /// Attempts to match the first, second and third tokens of the path with the given references
    /// </summary>
    template<typename _t1, typename _t2, typename _t3>
    bool match_path(_t1 &t1_ref, _t2 &t2_ref, _t3 &t3_ref)
    {
        if (m_path_tokens.size() < 3)
        {
            return false;
        }

        return
            this->bind(m_path_tokens[0], t1_ref) &&
            this->bind(m_path_tokens[1], t2_ref) &&
            this->bind(m_path_tokens[2], t3_ref);
    }

    /// <summary>
    /// Attempts to match a named query parameter with the given reference
    /// </summary>
    template<typename _t1>
    bool match_query(const std::string &key1, _t1 &t1_ref)
    {
        auto iter = m_query_tokens.find(utilities::conversions::utf8_to_utf16(key1));
        if (iter == m_query_tokens.end())
        {
            return this->bind(std::wstring(L""), t1_ref);
        }
        else
        {
            return this->bind(iter->second, t1_ref);
        }
    }

    /// <summary>
    /// Attempts to match a named query parameter with the given reference
    /// </summary>
    template<typename _t1>
    bool match_query(const std::wstring &key1, _t1 &t1_ref)
    {
        auto iter = m_query_tokens.find(key1);
        if (iter == m_query_tokens.end())
        {
            return this->bind(std::wstring(L""), t1_ref);
        }
        else
        {
            return this->bind(iter->second, t1_ref);
        }
    }

private:

    /// <summary>
    /// Binds an individual reference to a string value.
    /// </summary>
    template<typename _char_t, typename _t>
    bool bind(const std::basic_string<_char_t> &text, _t &ref)
    {
        std::basic_istringstream<_char_t> iss(text);
        iss >> ref;
        if (iss.fail() || !iss.eof())
        {
            return false;
        }

        return true;
    }

    /// <summary>
    /// Binds an optional reference to a string value.
    /// </summary>
    template<typename _char_t, typename _t>
    bool bind(const std::basic_string<_char_t> &text, optional<_t> &opt)
    {
        // if there's no provided value then it's not an error
        if (text.empty())
        {
            opt.has_value = false;
            return true;
        }

        // parsing errors are errors
        if(!bind(text, opt.value))
        {
            opt.has_value = false;
            return false;
        }
        opt.has_value = true;
        return true;
    }

    std::vector<std::wstring> m_path_tokens;
    std::map<std::wstring, std::wstring> m_query_tokens;
};

}